﻿Imports System.Windows
Imports System.Windows.Interop
Imports System.Windows.Media
Imports System.Windows.Controls
Imports System.ComponentModel
Imports System.Windows.Input
Imports System.Windows.Media.Imaging
Imports System.Windows.Shapes
Imports WinInterop = System.Windows.Interop
Imports System.Runtime.InteropServices

    Public MustInherit Class PT_CustomWindow
        Inherits Window

#Region "Déclarations et structures"
    ''' <summary>
    ''' POINT aka POINTAPI
    ''' </summary>
    <StructLayout(LayoutKind.Sequential)> _
    Public Structure POINT
        ''' <summary>
        ''' x coordinate of point.
        ''' </summary>
        Public x As Integer
        ''' <summary>
        ''' y coordinate of point.
        ''' </summary>
        Public y As Integer

        ''' <summary>
        ''' Construct a point of coordinates (x,y).
        ''' </summary>
        Public Sub New(ByVal x As Integer, ByVal y As Integer)
            Me.x = x
            Me.y = y
        End Sub
    End Structure
    <StructLayout(LayoutKind.Sequential)> _
    Public Structure MINMAXINFO
        Public ptReserved As Point
        Public ptMaxSize As Point
        Public ptMaxPosition As Point
        Public ptMinTrackSize As Point
        Public ptMaxTrackSize As Point
    End Structure
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Public Class MONITORINFO
        ''' <summary>
        ''' </summary>            
        Public cbSize As Integer = Marshal.SizeOf(GetType(MONITORINFO))

        ''' <summary>
        ''' </summary>            
        Public rcMonitor As New Rect()

        ''' <summary>
        ''' </summary>            
        Public rcWork As New Rect()

        ''' <summary>
        ''' </summary>            
        Public dwFlags As Integer = 0
    End Class
    <StructLayout(LayoutKind.Sequential, Pack:=0)> _
    Public Structure RECT
        ''' <summary> Win32 </summary>
        Public left As Integer
        ''' <summary> Win32 </summary>
        Public top As Integer
        ''' <summary> Win32 </summary>
        Public right As Integer
        ''' <summary> Win32 </summary>
        Public bottom As Integer

        ''' <summary> Win32 </summary>
        Public Shared ReadOnly Empty As New Rect()

        ''' <summary> Win32 </summary>
        Public ReadOnly Property Width() As Integer
            Get
                Return Math.Abs(right - left)
            End Get
        End Property

        ''' <summary> Win32 </summary>
        Public ReadOnly Property Height() As Integer
            Get
                Return bottom - top
            End Get
        End Property

        ''' <summary> Win32 </summary>
        Public Sub New(ByVal left As Integer, ByVal top As Integer, ByVal right As Integer, ByVal bottom As Integer)
            Me.left = left
            Me.top = top
            Me.right = right
            Me.bottom = bottom
        End Sub

        ''' <summary> Win32 </summary>
        Public Sub New(ByVal rcSrc As Rect)
            Me.left = rcSrc.left
            Me.top = rcSrc.top
            Me.right = rcSrc.right
            Me.bottom = rcSrc.bottom
        End Sub

        ''' <summary> Win32 </summary>
        Public ReadOnly Property IsEmpty() As Boolean
            Get
                ' BUGBUG : On Bidi OS (hebrew arabic) left > right
                Return left >= right OrElse top >= bottom
            End Get
        End Property

        ''' <summary> Return a user friendly representation of this struct </summary>
        Public Overloads Overrides Function ToString() As String
            If Me = RECT.Empty Then
                Return "RECT {Empty}"
            End If
            Return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }"
        End Function

        ''' <summary> Determine if 2 RECT are equal (deep compare) </summary>
        Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
            If Not (TypeOf obj Is RECT) Then
                Return False
            End If
            Return (Me = DirectCast(obj, RECT))
        End Function

        ''' <summary>Return the HashCode for this struct (not garanteed to be unique)</summary>
        Public Overloads Overrides Function GetHashCode() As Integer
            Return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode()
        End Function

        ''' <summary> Determine if 2 RECT are equal (deep compare)</summary>
        Public Shared Operator =(ByVal rect1 As Rect, ByVal rect2 As Rect) As Boolean
            Return (rect1.left = rect2.left AndAlso rect1.top = rect2.top AndAlso rect1.right = rect2.right AndAlso rect1.bottom = rect2.bottom)
        End Operator

        ''' <summary> Determine if 2 RECT are different(deep compare)</summary>
        Public Shared Operator <>(ByVal rect1 As Rect, ByVal rect2 As Rect) As Boolean
            Return Not (rect1 = rect2)
        End Operator

    End Structure

    <DllImport("user32")> _
    Friend Shared Function GetMonitorInfo(ByVal hMonitor As IntPtr, ByVal lpmi As MONITORINFO) As Boolean
    End Function
    <DllImport("User32")> _
    Friend Shared Function MonitorFromWindow(ByVal handle As IntPtr, ByVal flags As Integer) As IntPtr
    End Function
#End Region

#Region "Variables privées"
    Protected _minimizeButtonState As WindowButtonState
    Protected _maximizeButtonState As WindowButtonState
    ' including restore button
    Protected _closeButtonState As WindowButtonState

    Protected _minimizeButton As WindowMinimizeButton
    Protected _restoreButton As WindowRestoreButton
    Protected _maximizeButton As WindowMaximizeButton
    Protected _closeButton As WindowCloseButton

    Protected _windowButtons As UIElement
#End Region

#Region "Propriétés"
    ''' <summary>
    ''' Etat du bouton Minimize
    ''' </summary>
    Public Property MinimizeButtonState() As WindowButtonState
        Get
            Return _minimizeButtonState
        End Get
        Set(ByVal value As WindowButtonState)
            _minimizeButtonState = value
            OnWindowButtonStateChange(value, _minimizeButton)
        End Set
    End Property
    ''' <summary>
    ''' Etat du bouton Maximize
    ''' </summary>
    Public Property MaximizeButtonState() As WindowButtonState
        Get
            Return _maximizeButtonState
        End Get
        Set(ByVal value As WindowButtonState)
            _maximizeButtonState = value
            OnWindowButtonStateChange(value, _restoreButton)
            OnWindowButtonStateChange(value, _maximizeButton)
        End Set
    End Property
    ''' <summary>
    ''' Etat du bouton close
    ''' </summary>
    Public Property CloseButtonState() As WindowButtonState
        Get
            Return _closeButtonState
        End Get
        Set(ByVal value As WindowButtonState)
            _closeButtonState = value
            OnWindowButtonStateChange(value, _closeButton)
        End Set
    End Property
#End Region

#Region "Constructeur et initialisation"

    Public Sub New()
        _windowButtons = GenerateWindowButtons()
        Me.WindowStyle = WindowStyle.None
        Me.AllowsTransparency = True

        'this.Loaded += new RoutedEventHandler(OnLoaded);
        Me.AddHandler(LoadedEvent, New RoutedEventHandler(AddressOf OnLoaded))
        'Me.StateChanged += New EventHandler(AddressOf StandardWindow_StateChanged)

        ' buttons on active window and not active window differ => manage
        'Me.Activated += New EventHandler(AddressOf OnStandardWindowActivated)
        'Me.Deactivated += New EventHandler(AddressOf OnStandardWindowDeactivated)
    End Sub
    Sub win_SourceInitialized(ByVal sender As Object, ByVal e As EventArgs) Handles Me.SourceInitialized
        Dim handle As System.IntPtr = (New WinInterop.WindowInteropHelper(Me)).Handle
        WinInterop.HwndSource.FromHwnd(handle).AddHook(New WinInterop.HwndSourceHook(AddressOf WindowProc))
        WindowState = Windows.WindowState.Maximized
    End Sub
    Private Shared Function WindowProc(ByVal hwnd As System.IntPtr, ByVal msg As Integer, ByVal wParam As System.IntPtr, ByVal lParam As System.IntPtr, ByRef handled As Boolean) As System.IntPtr
        Select Case msg
            Case 36
                WmGetMinMaxInfo(hwnd, lParam)
                handled = True
        End Select

        Return CType(0, System.IntPtr)
    End Function
    Private Shared Sub WmGetMinMaxInfo(ByVal hwnd As System.IntPtr, ByVal lParam As System.IntPtr)

        Dim mmi As MINMAXINFO = DirectCast(Marshal.PtrToStructure(lParam, GetType(MINMAXINFO)), MINMAXINFO)

        ' Adjust the maximized size and position to fit the work area of the correct monitor
        Dim MONITOR_DEFAULTTONEAREST As Integer = 2
        Dim monitor As System.IntPtr = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)

        If monitor <> System.IntPtr.Zero Then

            Dim monitorInfo As New MONITORINFO()
            GetMonitorInfo(monitor, monitorInfo)
            Dim rcWorkArea As RECT = monitorInfo.rcWork
            Dim rcMonitorArea As RECT = monitorInfo.rcMonitor
            mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left)
            mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top)
            mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left)
            mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top)
        End If

        Marshal.StructureToPtr(mmi, lParam, True)
    End Sub

#End Region

#Region "Méthodes"

        Protected MustOverride Function GetWindowButtonsPlaceholder() As Decorator
        ''' <summary>
        ''' Création des boutons Window
        ''' </summary>
        Private Function GenerateWindowButtons() As UIElement
            'Les boutons
            _minimizeButton = New WindowMinimizeButton()
            _minimizeButton.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf OnButtonMinimize_Click))

            _restoreButton = New WindowRestoreButton()
            _restoreButton.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf OnButtonRestore_Click))

            _restoreButton.Margin = New Thickness(-1, 0, 0, 0)

            _maximizeButton = New WindowMaximizeButton()
            _maximizeButton.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf OnButtonMaximize_Click))
            _maximizeButton.Margin = New Thickness(-1, 0, 0, 0)

            _closeButton = New WindowCloseButton()
            _closeButton.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf OnButtonClose_Click))
            _closeButton.Margin = New Thickness(-1, 0, 0, 0)

            'Intégration des boutons dans un StackPanel
            Dim buttonsStackPanel As New StackPanel()
            buttonsStackPanel.Orientation = Orientation.Horizontal
            buttonsStackPanel.Children.Add(_minimizeButton)
            buttonsStackPanel.Children.Add(_restoreButton)
            buttonsStackPanel.Children.Add(_maximizeButton)
            buttonsStackPanel.Children.Add(_closeButton)

            Return buttonsStackPanel
        End Function

        ''' <summary>
        ''' called when window is loaded; extends Aero glass effect
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' put Window Buttons into placeholder
            Dim placeholder As Decorator = GetWindowButtonsPlaceholder()

            If placeholder Is Nothing Then
                Throw New NotSupportedException("Placeholder must be created already in the initialization of the Window")
            End If

            placeholder.Child = _windowButtons

            ' refresh state
            OnStateChanged(New EventArgs())
        End Sub

        ''' <summary>
        ''' Called when Minimize button clicked
        ''' </summary>
        ''' <param name="sender">Button</param>
        ''' <param name="e">Event args</param>
        Protected Overridable Sub OnButtonMinimize_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Me.WindowState = WindowState.Minimized
        End Sub

        ''' <summary>
        ''' Called when Restore button clicked
        ''' </summary>
        ''' <param name="sender">Button</param>
        ''' <param name="e">Event args</param>
        Protected Overridable Sub OnButtonRestore_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Me.WindowState = WindowState.Normal
        End Sub

        ''' <summary>
        ''' Called when Maximize button clicked
        ''' </summary>
        ''' <param name="sender">Button</param>
        ''' <param name="e">Event args</param>
        Protected Overridable Sub OnButtonMaximize_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Me.WindowState = WindowState.Maximized
        End Sub

        ''' <summary>
        ''' Called when Close button clicked
        ''' </summary>
        ''' <param name="sender">Button</param>
        ''' <param name="e">Event args</param>
        Protected Overridable Sub OnButtonClose_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Me.Close()
        End Sub

        ''' <summary>
        ''' called when state of the window changed to minimized, normal or maximized
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Private Sub StandardWindow_StateChanged(ByVal sender As Object, ByVal e As EventArgs) Handles Me.StateChanged
            If Me.WindowState = WindowState.Normal Then
                Me._restoreButton.Visibility = Visibility.Collapsed

                ' if Maximize button state is 'None' => do not make visible
                If _maximizeButtonState <> WindowButtonState.None Then
                    Me._maximizeButton.Visibility = Visibility.Visible
                End If
            ElseIf Me.WindowState = WindowState.Maximized Then
                Me._maximizeButton.Visibility = Visibility.Collapsed

                ' if Maximize button state is 'None' => do not make visible
                If _maximizeButtonState <> WindowButtonState.None Then
                    Me._restoreButton.Visibility = Visibility.Visible
                End If
            End If
        End Sub

        ''' <summary>
        ''' Helper Function
        ''' </summary>
        ''' <param name="state"></param>
        ''' <param name="button"></param>
        ''' <remarks></remarks>
        Protected Overridable Sub OnWindowButtonStateChange(ByVal state As WindowButtonState, ByVal button As WindowButton)
            Select Case state
                Case WindowButtonState.Normal
                    button.Visibility = Visibility.Visible
                    button.IsEnabled = True
                    Exit Select

                Case WindowButtonState.Disabled
                    button.Visibility = Visibility.Visible
                    button.IsEnabled = False
                    Exit Select

                Case WindowButtonState.None
                    button.Visibility = Visibility.Collapsed
                    Exit Select
            End Select
        End Sub

#End Region

#Region "Gestion Activation/Désactivation de la fenêtre"
        Protected Overridable Sub OnStandardWindowActivated(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Activated
            _minimizeButton.Background = _minimizeButton.BackgroundDefaultValue
            _maximizeButton.Background = _maximizeButton.BackgroundDefaultValue
            _restoreButton.Background = _restoreButton.BackgroundDefaultValue
            _closeButton.Background = _closeButton.BackgroundDefaultValue
        End Sub
        Protected Overridable Sub OnStandardWindowDeactivated(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Deactivated
            _minimizeButton.Background = Brushes.Transparent
            _maximizeButton.Background = Brushes.Transparent
            _restoreButton.Background = Brushes.Transparent
            _closeButton.Background = Brushes.Transparent
        End Sub
#End Region

    End Class